﻿#if UNITY_EDITOR
//#define DEBUG_ACTION
#endif
using Devil;
using LitJson;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using UnityEngine;

namespace GameToolkit
{

    /*
     * 确保 GameInput 执行顺序保持在最早更新
     * Input Define
     * "category"
     * {
     *      name              :string (input name)
     *      {
     *          axis          :bool (axis or button)
     *          readonly      :bool (readonly)
     *          desc          :string (description)
     *          bind          :Bind (binding controller)
     *          {
     *              "controller"
     *              {
     *                  key          :string (controller's key)
     *                  axis         :string (controller's axis)
     *                  nigative     :string (controller's key for axis' nigative)
     *                  positive     :string (controller's key for axis' positive)
     *                  sensitivity  :float  (axis' sensitivity)
     *              }
     *          }
     *      
     *      }
     * }
     */

    using IInputActionHook = InputController.IInputActionHook;
    using KeyInfo = InputController.KeyInfo;
    using IInputAction = InputController.IInputAction;

    [System.AttributeUsage(System.AttributeTargets.Field, AllowMultiple = false)]
    public class GameInputAttribute : PropertyAttribute
    {
        public string Category { get; set; }
    }

    [System.AttributeUsage(System.AttributeTargets.Field, AllowMultiple = false)]
    public class GameButtonAttribute : GameInputAttribute { }

    [System.AttributeUsage(System.AttributeTargets.Field, AllowMultiple = false)]
    public class GameAxisAttribute : GameInputAttribute { }

    [DefaultExecutionOrder(-2000)]
    public class GameInput : MonoBehaviour
    {
        public enum ECreateInputType
        {
            None,
            Button,
            Axis,
        }

        // 本地覆盖配置文件
        readonly string mOverrideFile = "InputCfg.json";

        #region Declare
        public enum EBlockLevel
        {
            None,
            NoInteract = 1,
            Player = 10,
            UI = 20,
            Status = 30,
        }

        public enum EAxisValue
        {
            None,
            Positive,
            Nigative,
        }

        /// <summary>
        /// 控制器事件拦截组件
        /// </summary>
        public interface IInputBlocker
        {
            EBlockLevel BlockLevel { get; }
            bool IsCursorEnabled { get; }
            //bool IsInputEnabled { get; set; }
        }

        public interface IConfigurable
        {
            void SetConfig(JsonData data);
            JsonData GetConfig();
        }

        public interface ICombinedAxis : IInputAction
        {
            KeyInfo GetNigativeKeyInfo();
            KeyInfo GetPositiveKeyInfo();
        }

        public interface IInput
        {
            string Title { get; }
            string InputName { get; }
            bool IsDown { get; }
            bool IsUp { get; }
            bool HasButton { get; }
            bool IsReadonly { get; }
            // bool SetAction(IInputAction action);
        }

        public interface IButton : IInput
        {
            bool SetAction(IInputAction action);
            KeyInfo GetKeyInfo(InputController controller);
        }

        public interface IAxis : IInput
        {
            bool SetAction(IInputAction action);
            string NigativeTitle { get; }
            string PositiveTitle { get; }
            float Value { get; }
            void GetKeyInfo(InputController controller, out KeyInfo nigative, out KeyInfo positive);
        }

        static GameInput sActiveInst;
        static object sLock = new object();
        public static GameInput Current
        {
            get
            {
                lock (sLock)
                {
                    return sActiveInst;
                }
            }
        }
        static EBlockLevel sBlockLv;
        static List<IInputBlocker> sBlockers = new List<IInputBlocker>();

        static InputController sController;
        //static EControllerType sControllerType;
        static KeyInfo sKey;
        public static event System.Action<InputController> OnControllerChanged;
        //public static event System.Action OnReloadInputs;
        /// <summary>
        /// 当前控制器类型
        /// </summary>
        public static EControllerType ActiveControllerType { get { return sKey.type; } }

        /// <summary>
        /// 当前输入是否为轴类型
        /// </summary>
        public static bool IsCurrentAxisAction { get { return sKey.isAxis; } }

        /// <summary>
        /// 当前控制器
        /// </summary>
        public static InputController ActiveController { get { return sController; } }

        /// <summary>
        /// 任意键按下
        /// </summary>
        public static bool IsAnyButtonDown { get { return sActiveInst == null ? false : sActiveInst.mAnyButtonDown; } }
        /// <summary>
        /// 任意键抬起
        /// </summary>
        public static bool IsAnyButtonUp { get { return sActiveInst == null ? false : sActiveInst.mAnyButtonUp; } }
        /// <summary>
        /// 任意键按住
        /// </summary>
        public static bool IsAnyButton { get { return sActiveInst == null ? false : sActiveInst.mAnyButton; } }

        static void SetActiveController(InputController ct, KeyInfo key)
        {
            if (ct != null && sController != ct)
            {
                sController = ct;
                var tp = ct.IsKindOf(key.type) ? key.type : ct.ControllerType;
                sKey = new KeyInfo(tp, key.isAxis, key.customizable, key.keyName, key.keyCode);
                OnControllerChanged?.Invoke(ct);
            }
            else if(sController != null && sController.IsKindOf(key.type))
            {
                sKey = key;
            }
        }

        public static void NotifyControllerChanged(InputController ct)
        {
            if(ct != null && ct == sController)
            {
                OnControllerChanged?.Invoke(ct);
            }
        }

        public static int Vibrate(InputController.EVibrationID id, float strength, float duration = 0.2f)
        {
            var ct = sController as InputController.IInputVibrate;
            if (ct != null && Current != null && Current.m_EnableVibration)
            {
                return ct.StartVibration(id, strength, duration);
            }
            return 0;
        }

        public static IInputBlocker GetBlocker()
        {
            var len = sBlockers.Count;
            return len > 0 ? sBlockers[len - 1] : null;
        }

        public static EBlockLevel GetBlockLevel()
        {
            return sBlockLv;
        }

        public static void SetCursor(bool visible)
        {
            //var lockstate = visible || !locked ? CursorLockMode.None : CursorLockMode.Locked; 
            if (visible != Cursor.visible /*|| lockstate != Cursor.lockState*/)
            {
                Cursor.visible = visible;
                Cursor.lockState = visible ? CursorLockMode.None : CursorLockMode.Locked;
            }
        }

        public static void AddBlocker(IInputBlocker block)
        {
            var len = sBlockers.Count;
            var index = sBlockers.IndexOf(block);
            if (index == -1)
            {
                //if (len > 0)
                //    sBlockers[len - 1].IsInputEnabled = false;
                sBlockers.Add(block);
                sBlockLv = (EBlockLevel)Mathf.Max((int)block.BlockLevel, (int)sBlockLv);
                //block.IsInputEnabled = true;
            }
            else if (index < len - 1)
            {
                //sBlockers[len - 1].IsInputEnabled = false;
                sBlockers.RemoveAt(index);
                sBlockers.Add(block);
                //block.IsInputEnabled = true;
            }
            //SetCursor((sController == null || sController.IsKindOf(EControllerType.Mouse)) && block.IsCursorEnabled);
        }

        public static void RemoveBlocker(IInputBlocker block)
        {
            var index = sBlockers.IndexOf(block);
            if (index == -1)
                return;
            //block.IsInputEnabled = false;
            sBlockers.RemoveAt(index);
            //sBlockLv = EBlockLevel.None;
            //for(int i=0;i< sBlockers.Count;i++)
            //{
            //    var lv = sBlockers[i].BlockLevel;
            //    if (lv > sBlockLv)
            //        sBlockLv = lv;
            //}
            //if (sBlockers.Count > 0)
            //    sBlockers[sBlockers.Count - 1].IsInputEnabled = true;
            //UpdateCursor();
        }

        static EBlockLevel CalculateBlockLv()
        {
            var lv = EBlockLevel.None;
            var len = sBlockers.Count;
            for (int i = len - 1; lv < EBlockLevel.Status && i >= 0; i--)
            {
                var v = sBlockers[i].BlockLevel;
                if (v > lv)
                    lv = v;
            }
            return lv;
        }

        public static void GetAllGroupTitles(ICollection<string> groups)
        {
            if (sActiveInst != null)
            {
                sActiveInst.GetGroupTitles(groups);
            }
        }

        /// <summary>
        /// 获取所有可用控制器
        /// </summary>
        /// <param name="controller"></param>
        public static void GetAllControllers(ICollection<InputController> controller)
        {
            if (sActiveInst != null && sActiveInst.m_InputModules != null)
            {
                for (int i = 0; i < sActiveInst.m_InputModules.Length; i++)
                {
                    var mod = sActiveInst.m_InputModules[i];
                    if (mod != null)
                        controller.Add(mod);
                }
            }
        }

        public static InputController GetController(EControllerType type)
        {
            InputController ctrl = null;
            if (sActiveInst != null && sActiveInst.m_InputModules != null)
            {
                for (int i = 0; i < sActiveInst.m_InputModules.Length; i++)
                {
                    var mod = sActiveInst.m_InputModules[i];
                    if (mod != null && mod.IsKindOf(type))
                    {
                        ctrl = mod;
                        if (ctrl.IsConnected)
                            return ctrl;
                    }
                }
            }
            return ctrl;
        }

        public static void GetAllButtons(int groupId, ICollection<IButton> btns)
        {
            if (sActiveInst != null)
            {
                for (int i = 0; i < sActiveInst.mButtons.Count; i++)
                {
                    var btn = sActiveInst.mButtons[i];

                    if ((groupId == 0 || btn.GroupID == groupId) && btn.IsVaild)
                        btns.Add(btn);
                }
            }
        }

        public static void GetAllAxis(int groupId, ICollection<IAxis> axis)
        {
            if (sActiveInst != null)
            {
                for (int i = 0; i < sActiveInst.mAxises.Count; i++)
                {
                    var ax = sActiveInst.mAxises[i];
                    if ((groupId == 0 || groupId == ax.GroupID) && ax.IsVaild)
                        axis.Add(ax);
                }
            }
        }

        public static IInput GetInput(string inputName, ECreateInputType createType = ECreateInputType.None)
        {
            lock (sLock)
            {
                var inst = sActiveInst;
                if (inst == null)
                    return null;
                IInput input = inst.GetButtonDef(inputName);
                if (input == null)
                    input = inst.GetAxisDef(inputName);
                if(input == null)
                {
                    if(createType == ECreateInputType.Button)
                    {
                        var btn = new Button(inputName);
                        inst.mButtons.Add(btn);
                        input = btn;
                    }
                    else if(createType == ECreateInputType.Axis)
                    {
                        var axis = new Axis(inputName);
                        inst.mAxises.Add(axis);
                        input = axis;
                    }
                }
                return input;
            }
        }

        public static IAxis GetAxis(string axisName, bool autoCreate = false)
        {
            lock (sLock)
            {
                var inst = sActiveInst;
                if (inst == null)
                    return null;// new InputSystemAxis(axisName);
                var axis = inst.GetAxisDef(axisName);
                if (axis == null && autoCreate)
                {
                    axis = new Axis(axisName);
                    inst.mAxises.Add(axis);
//#if UNITY_EDITOR
//                    Debug.LogFormat("[GameInput] Auto Create Axis: \"{0}\"", axisName);
//#endif
                }
                return axis;
            }
        }

        public static IButton GetButton(string buttonName, bool autoCreate = false)
        {
            lock (sLock)
            {
                var inst = sActiveInst;
                if (inst == null)
                    return null;// new InputSystemAxis(buttonName);
                var btn = inst.GetButtonDef(buttonName);
                if (btn == null && autoCreate)
                {
                    btn = new Button(buttonName);
                    inst.mButtons.Add(btn);
//#if UNITY_EDITOR
//                    Debug.LogFormat("[GameInput] Auto Create Button: \"{0}\"", buttonName);
//#endif
                }
                return btn;
            }
        }

        #endregion

        #region implement

#pragma warning disable CS0649

        [Header("Resources/InputDefine/Input.json")]
        [SerializeField]
        private bool m_DefaultCursorEnabled = true;

        [SerializeField, Tooltip("测试手柄连接间隔时间(秒)")]
        float m_TestJoystickDuration = 2f;

        [SerializeField]
        bool m_EnableVibration = true;

        [SerializeField]
        private InputController[] m_InputModules;
#pragma warning restore

        HashSet<string> mGroupTitles;
        private List<Axis> mAxises;
        private List<Button> mButtons;
        private List<BindingAction> mBindingActions;
        private List<ITick> mBindingTicks;
        string mDefaultCfg;
        bool mIsDirty;
        bool mAnyButtonDown;
        bool mAnyButtonUp;
        bool mAnyButton;
        float mTestDuration;
        bool mForceTestConnection;
        // 当前输入(每帧更新)
        IInput[] mInputBuffer;
        int mInputBufferSize;

        public bool IsVibrationEnabled
        {
            get { return m_EnableVibration; }
            set { m_EnableVibration = value; }
        }

        [ContextMenu("Save Input Config")]
        public void MarkDirty()
        {
            mIsDirty = true;
            ParallelUtils.Schedule(() =>
            {
                lock (sLock)
                {
                    if (mIsDirty)
                    {
                        mIsDirty = false;
                        UnregistBindingActions();
                        var file = NativeFileSystem.Combine(ParallelUnity.persistentDataPath, mOverrideFile);
                        SaveInputsConfig(file);
                    }
                }
            }, false);
        }


        public InputController GetConnectedController(EControllerType type)
        {
            lock (sLock)
            {
                var len = m_InputModules == null ? 0 : m_InputModules.Length;
                for (int i = 0; i < len; i++)
                {
                    var controller = m_InputModules[i];
                    if (controller != null && controller.IsKindOf(type) && controller.IsConnected)
                        return controller;
                }
            }
            return null;
        }


        public void LoadInputConfig(bool loadAsDefault)
        {
            lock (sLock)
            {
                mIsDirty = false;

                mBindingTicks.Clear();
                mBindingActions.Clear();

                for (int i = 0; i < mButtons.Count; i++)
                {
                    mButtons[i].ClearActions();
                }
                for (int i = 0; i < mAxises.Count; i++)
                {
                    mAxises[i].ClearActions();
                }

                bool mMerge = false;
                if (!string.IsNullOrEmpty(mDefaultCfg))
                {
                    var json = JsonMapper.ToObject(mDefaultCfg);
                    LoadInputs(json, mMerge);
                    mMerge = true;
                    System.Comparison<InputBehaviour> comparion = (a, b) =>
                    {
                        if (a == null)
                            return 1;
                        if (b == null)
                            return -1;
                        return a.index - b.index;
                    };
                    mButtons.Sort(comparion);
                    mAxises.Sort(comparion);
                }

                if (!loadAsDefault)
                {
                    var file = NativeFileSystem.Combine(ParallelUnity.persistentDataPath, mOverrideFile);
                    try
                    {
                        var txt = NativeFileSystem.ReadAllText(file);
                        if (!string.IsNullOrEmpty(txt))
                        {
                            var json = JsonMapper.ToObject(txt);
                            LoadInputs(json, mMerge);
                        }
                    }
                    catch (System.Exception e)
                    {
                        Debug.LogException(e);
                    }
                }
                UnregistBindingActions();
                mForceTestConnection = true;
            }
            //OnReloadInputs?.Invoke();
        }

        public void GetGroupTitles(ICollection<string> groupTitles)
        {
            lock (sLock)
            {
                foreach (var dt in mGroupTitles)
                {
                    groupTitles.Add(dt);
                }
            }
        }

        void ValidateInputsConnection(bool alwaysValidate)
        {
            var validate = alwaysValidate;
            var len = m_InputModules.Length;
            var connected = false;
            for (int i = 0; i < len; i++)
            {
                var module = m_InputModules[i];
                validate |= module != null && module.ValidateConnectionChanged(!connected);
                connected |= module.IsConnected;
            }
            if (validate)
            {
                if(sController != null && !sController.IsConnected)
                {
                    InputController current = null;
                    for (int i = 0; i < len; i++)
                    {
                        var module = m_InputModules[i];
                        if(module != null && module.IsConnected)
                        {
                            if (module.IsDefault || current == null)
                                current = module;
                        }
                    }
                    SetActiveController(current, default);
                }
                mBindingTicks.Clear();
                for (int i = 0; i < len; i++)
                {
                    var module = m_InputModules[i];
                    if (module != null && module.IsConnected && (module is ITick))
                        mBindingTicks.Add((ITick)module);
                }
                for (int i = 0; i < mBindingActions.Count; i++)
                {
                    var act = mBindingActions[i].action;
                    if (act.Controller.IsConnected && (act is ITick))
                        mBindingTicks.Add((ITick)act);
                }
            }
        }

        void LoadInputs(JsonData json, bool merge)
        {
            int count = 0;
            //ResetInputs();
            foreach (var catname in json.Keys)
            {
                var cat = json[catname];
                if (cat == null)
                    continue;
                //var group = StringUtil.EqualIgnoreCase(catname, "default") ? new TextRef(0, catname) : new TextRef(catname);
                mGroupTitles.Add(catname);
                foreach (var input in cat.Keys)
                {
                    var cfg = cat[input];
                    if (cfg == null)
                        continue;
                    bool isAxis;
                    InputBehaviour inputDef = null;
                    if (merge)
                    {
                        inputDef = GetAxisDef(input);
                        if (inputDef == null)
                            inputDef = GetButtonDef(input);
                        isAxis = inputDef is IAxis;
                    }
                    else
                    {
                        isAxis = (bool)cfg["axis"];
                        if (isAxis)
                        {
                            inputDef = GetAxisDef(input);
                            if (inputDef == null)
                            {
                                inputDef = new Axis(input);
                                mAxises.Add((Axis)inputDef);
                            }
                        }
                        else
                        {
                            inputDef = GetButtonDef(input);
                            if (inputDef == null)
                            {
                                inputDef = new Button(input);
                                mButtons.Add((Button)inputDef);
                            }
                        }
                        inputDef.index = count++;
                    }
                    if (inputDef != null)
                    {
                        inputDef.ReadConfig(this, catname.GetHashCode(), cfg, merge);
                        if (!inputDef.IsVaild)
                            Debug.LogErrorFormat("[GameInput] Invaild {0}: \"{1}\"", isAxis ? "Axis" : "Button", inputDef.InputName);
                    }
                }
            }
        }

        void SaveInputsConfig(string outputFile)
        {
            SaveAsJson(outputFile);
        }

        void SaveAsJson(string outputFile)
        {
            lock (sLock)
            {
                var inputs = new JsonData(JsonType.Object);
                Dictionary<int, JsonData> groups = new Dictionary<int, JsonData>();
                foreach(var grp in mGroupTitles)
                {
                    var dt = new JsonData(JsonType.Object);
                    groups[grp.GetHashCode()] = dt;
                    inputs[grp] = dt;
                }
                var defaultCat = inputs["default"];
                if (defaultCat == null)
                {
                    defaultCat = new JsonData(JsonType.Object);
                    inputs["default"] = defaultCat;
                }
                for (int i = 0; i < mAxises.Count; i++)
                {
                    var axis = mAxises[i];
                    var cfg = axis.CreateConfig(this);
                    if (cfg == null)
                        continue;
                    JsonData cat;
                    if(!groups.TryGetValue(axis.GroupID, out cat))
                    {
                        cat = defaultCat;
                    }
                    cat[axis.InputName] = cfg;
                }
                for (int i = 0; i < mButtons.Count; i++)
                {
                    var btn = mButtons[i];
                    var cfg = btn.CreateConfig(this);
                    if (cfg == null)
                        continue;
                    JsonData cat;
                    if (!groups.TryGetValue(btn.GroupID, out cat))
                    {
                        cat = defaultCat;
                    }
                    cat[btn.InputName] = cfg;
                }

                if (!string.IsNullOrEmpty(outputFile))
                {
                    var dic = NativeFileSystem.GetFolderName(outputFile);
                    NativeFileSystem.EnsureDirectory(dic);
                    var write = NativeFileSystem.WriteAllText(outputFile, inputs.ToJson(), System.Text.Encoding.UTF8);
                    if (write)
                        Debug.LogFormat("[GameInput] Save Input Config @ \"{0}\"", outputFile);
                }
                else
                {
                    Debug.LogFormat("[GameInput] Input Config: \n{0}", inputs.ToJson());
                }
            }
        }

        // 删除未注册按键
        public void UnregistBindingActions()
        {
            lock (sLock)
            {
                for (int i = mBindingActions.Count - 1; i >= 0; i--)
                {
                    var bind = mBindingActions[i];
                    bool isRegist = false;
                    for (int k = 0; !isRegist && k < mButtons.Count; k++)
                    {
                        isRegist |= mButtons[k].IsRegist(bind.action);
                    }
                    for (int k = 0; !isRegist && k < mAxises.Count; k++)
                    {
                        isRegist |= mAxises[k].IsRegist(bind.action);
                    }
                    if (!isRegist)
                    {
                        mBindingActions.RemoveAt(i);
                        if (bind.action is ITick)
                            mBindingTicks.Remove((ITick)bind.action);
                    }
                }
            }
        }

        void PrepareInputBinding()
        {
            if (mGroupTitles == null)
                mGroupTitles = new HashSet<string>();
            else
                mGroupTitles.Clear();
            if (mBindingActions == null)
                mBindingActions = new List<BindingAction>(64);
            else
                mBindingActions.Clear();
            if (mBindingTicks == null)
                mBindingTicks = new List<ITick>();
            else
                mBindingTicks.Clear();
            if (mButtons == null)
                mButtons = new List<Button>();
            else
                mButtons.Clear();
            if (mAxises == null)
                mAxises = new List<Axis>();
            else
                mAxises.Clear();

            var len = m_InputModules == null ? 0 : m_InputModules.Length;
            for (int i = 0; i < len; i++)
            {
                var bind = m_InputModules[i];
                if (bind != null)
                {
                    bind.Prepare(this);
                    if (bind.IsDefault || sController == null)
                        SetActiveController(bind, default);
                }
            }
        }

        Button GetButtonDef(string btnName)
        {
            var id = btnName.IgnoreCaseToHash();
            for (int i = 0; i < mButtons.Count; i++)
            {
                var btn = mButtons[i];
                if (btn.ID == id)
                    return btn;
            }
            return null;
        }

        Axis GetAxisDef(string btnName)
        {
            var id = btnName.IgnoreCaseToHash();
            for (int i = 0; i < mAxises.Count; i++)
            {
                var axis = mAxises[i];
                if (axis.ID == id)
                    return axis;
            }
            return null;
        }

        BindingAction GetBindingAction(int id)
        {
            for (int i = 0; i < mBindingActions.Count; i++)
            {
                var key = mBindingActions[i];
                if (key.id == id)
                    return key;
            }
            return null;
        }

        int GetActionID(InputController ctrl, int key1, int key2)
        {
            return MathExt.Hash(1, ctrl.TypeIdentifier, key1, key2);
        }

        /// <summary>
        /// 注册默认输入响应
        /// </summary>
        /// <param name="controller"></param>
        /// <param name="key"></param>
        /// <returns></returns>
        public IInputAction RegistAction(string controller, string key)
        {
            var len = m_InputModules == null ? 0 : m_InputModules.Length;
            if (len == 0 || string.IsNullOrEmpty(key))
                return null;
            var ctid = controller.IgnoreCaseToHash();
            lock (sLock)
            {
                for (int i = 0; i < len; i++)
                {
                    var bind = m_InputModules[i];
                    if (bind != null && bind.TypeIdentifier == ctid)
                    {
                        return RegistAction(bind, new NameID(key));
                    }
                }
            }
            return null;
        }

        IInputAction RegistAction(InputController ctrl, NameID key)
        {
            if (key.id == 0)
                return null;
            var id = GetActionID(ctrl, key.id, 0);
            var act = GetBindingAction(id);
            if (act == null)
            {
                IInputAction action;
                var c = key.name[0];
                if (c == '+')
                {
                    var fromAxis = new NameID(key.name.Substring(1));
                    var axis = RegistAction(ctrl, fromAxis);
                    action = axis == null ? null : new ActionFromValue(axis, EAxisValue.Positive);
                }
                else if (c == '-')
                {
                    var fromAxis = new NameID(key.name.Substring(1));
                    var axis = RegistAction(ctrl, fromAxis);
                    action = axis == null ? null : new ActionFromValue(axis, EAxisValue.Nigative);
                }
                else
                {
                    action = ctrl.GetAction(key.name);
                }
                if (action != null)
                {
                    act = new BindingAction(id, action);
                    mBindingActions.Add(act);
                    if (ctrl.IsConnected && (action is ITick))
                        mBindingTicks.Add((ITick)action);
                }
                return action;
            }
            else
            {
                return act.action;
            }
        }

        /// <summary>
        /// 注册轴类型输入动作响应
        /// </summary>
        /// <param name="controller"></param>
        /// <param name="positive"></param>
        /// <param name="nigative"></param>
        /// <returns></returns>
        public IInputAction RegistAxis(string controller, string positive, string nigative)
        {
            var len = m_InputModules == null ? 0 : m_InputModules.Length;
            if (len == 0)
                return null;
            var ctid = controller.IgnoreCaseToHash();
            lock (sLock)
            {
                for (int i = 0; i < len; i++)
                {
                    var bind = m_InputModules[i];
                    if (bind != null && bind.TypeIdentifier == ctid)
                    {
                        var pos = new NameID(positive);
                        var nig = new NameID(nigative);
                        if (pos.id == nig.id && pos.id == 0)
                        {
                            return null;
                        }
                        else
                        {
                            return RegistAxis(bind, pos, nig);
                        }
                    }
                }
            }
            return null;
        }

        IInputAction RegistAxis(InputController ctrl, NameID positive, NameID nigative)
        {
            if (positive.id == 0 && nigative.id == 0)
                return null;
            var id = GetActionID(ctrl, positive.id, nigative.id);
            var act = GetBindingAction(id);
            if (act == null)
            {
                IInputAction axis = null;
                if (nigative.id == positive.id && nigative.id != 0)
                {
                    axis = ctrl.GetAction(positive.name);
                }
                else if (nigative.id != 0 || positive.id != 0)
                {
                    var nigact = RegistAction(ctrl, nigative);// bind.GetAction(nigative);
                    var posact = RegistAction(ctrl, positive);
                    if (nigact != null || posact != null)
                        axis = new AxisFromAction(nigact, posact);
                }

                if (axis != null)
                {
                    act = new BindingAction(id, axis);
                    mBindingActions.Add(act);
                    if (ctrl.IsConnected && (axis is ITick))
                        mBindingTicks.Add((ITick)axis);
                }
                return axis;
            }
            else
            {
                return act.action;
            }
        }

        /// <summary>
        /// 当前输入数量
        /// </summary>
        public int CurrentInputCount { get { return mInputBufferSize; } }

        /// <summary>
        /// 获取当前的输入
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public IInput GetCurrentInput(int index)
        {
            return mInputBuffer[index];
        }

        private void Awake()
        {
            mInputBuffer = new IInput[8];
            PrepareInputBinding();
        }

        private void Start()
        {
            if (sActiveInst == this)
            {
                MainThread.Setup();
                //if (m_InputModules != null && m_InputModules.Length > 0)
                //    SetActiveController(m_InputModules[0]);
                //PrepareInputBinding();
                var asset = Resources.Load<TextAsset>("InputDefine/Input");
                mDefaultCfg = asset.text;
                if (!string.IsNullOrEmpty(mDefaultCfg))
                {
                    ParallelUtils.Schedule(LoadInputConfig, false, false);
                }

            }
        }

        private void OnEnable()
        {
            lock (sLock)
            {
                mForceTestConnection = true;
                if (sActiveInst == null || sActiveInst == this)
                {
                    sActiveInst = this;
                }
                else
                {
                    enabled = false;
                }
            }
        }

        private void OnDisable()
        {
            mInputBufferSize = 0;
            lock (sLock)
            {
                if (sActiveInst == this)
                {
                    sActiveInst = null;
                    SetCursor(true);
                }
            }
        }

        private void OnDestroy()
        {
            var len = m_InputModules == null ? 0 : m_InputModules.Length;
            for (int i = 0; i < len; i++)
            {
                var bind = m_InputModules[i];
                if (bind != null)
                {
                    bind.Release(this);
                }
            }
        }

        private void Update()
        {
            var enableCursor = sBlockers.Count > 0 ? sBlockers[sBlockers.Count - 1].IsCursorEnabled : m_DefaultCursorEnabled;
            var iscontroller = sController != null && !sController.IsKindOf(EControllerType.Mouse);
            SetCursor(enableCursor && !iscontroller);
            sBlockLv = CalculateBlockLv();
            var deltaTime = Time.unscaledDeltaTime;
            lock (sLock)
            {
                mTestDuration += deltaTime;
                if (mForceTestConnection || mTestDuration >= m_TestJoystickDuration)
                {
                    ValidateInputsConnection(mForceTestConnection);
                    mForceTestConnection = false;
                    mTestDuration = 0;
                }
                for (int i = 0; i < mBindingTicks.Count; i++)
                {
                    mBindingTicks[i].OnTick(deltaTime);
                }
                mInputBufferSize = 0;
                IInputAction ct = null;
                mAnyButton = false;
                mAnyButtonDown = false;
                mAnyButtonUp = false;
                for (int i = 0; i < mAxises.Count; i++)
                {
                    var ax = mAxises[i];
                    var vail = ax.ValidateState(deltaTime);
                    if (vail != null)
                        ct = vail;
                    mAnyButton |= ax.HasButton;
                    mAnyButtonDown |= ax.IsDown;
                    mAnyButtonUp |= ax.IsUp;
                    if (ax.IsDown || ax.IsUp || ax.HasButton)
                    {
                        NativeArrayUtil.EnsureNativeSize(ref mInputBuffer, mInputBufferSize, mInputBufferSize + 1);
                        mInputBuffer[mInputBufferSize++] = ax;
                    }
                }
                for (int i = 0; i < mButtons.Count; i++)
                {
                    var btn = mButtons[i];
                    var vail = btn.ValidateState(deltaTime);
                    if (vail != null)
                        ct = vail;
                    mAnyButton |= btn.HasButton;
                    mAnyButtonDown |= btn.IsDown;
                    mAnyButtonUp |= btn.IsUp;
                    if(btn.IsDown || btn.IsUp || btn.HasButton)
                    {
                        NativeArrayUtil.EnsureNativeSize(ref mInputBuffer, mInputBufferSize, mInputBufferSize + 1);
                        mInputBuffer[mInputBufferSize++] = btn;
                    }
                }
                if (ct != null)
                    SetActiveController(ct.Controller, ct.GetKeyInfo());
                ValidateInputActionHooks();
            }
        }

        #endregion

        #region Editor

#if UNITY_EDITOR

        [ContextMenu("Reset Default Config")]
        void LoadDefaultInputConfig()
        {
            System.Action act = () =>
            {
                var asset = Resources.Load<TextAsset>("InputDefine/Input");
                mDefaultCfg = asset.text;
            };
            if (MainThread.IsMainThread)
            {
                act();
            }
            else
            {
                var exc = ParallelUtils.Schedule(act, true);
                exc.WaitComplete();
            }
            if (mBindingActions == null)
                PrepareInputBinding();
            LoadInputConfig(true);
        }

        [UnityEditor.MenuItem("Assets/Generate Input Define C# Script")]
        static void GenerateInputDefineScript()
        {
            var folder = GlobalUtil.ActiveProjectFolder;
            if (string.IsNullOrEmpty(folder))
                return;
            var file = Resources.Load<TextAsset>("InputDefine/Input");
            var filename = UnityEditor.AssetDatabase.GetAssetPath(file);
            Debug.Assert(file != null, "[GameInput] Don't find resource of \"InputDefine/Input.xml\"");
            UnityEditor.AssetDatabase.StartAssetEditing();
            string keyword_pattern = @"^[a-zA-Z]+[_a-zA-Z0-9 ]*$";
            HashSet<string> defineFiles = new HashSet<string>();
            Dictionary<string, List<string>> buttons = new Dictionary<string, List<string>>();
            Dictionary<string, List<string>> axis = new Dictionary<string, List<string>>();
            try
            {
                var json = JsonMapper.ToObject(file.text);
                foreach (var key in json.Keys)
                {
                    var def = key.ContainsIgnoreCase("UI") ? "InputDefine_UI" : "InputDefine";
                    defineFiles.Add(def);
                    var content = json[key];
                    List<string> btns, axises;
                    if (!buttons.TryGetValue(def, out btns))
                    {
                        btns = new List<string>();
                        buttons[def] = btns;
                    }
                    if (!axis.TryGetValue(def, out axises))
                    {
                        axises = new List<string>();
                        axis[def] = axises;
                    }
                    foreach (var kname in content.Keys)
                    {
                        if (!Regex.IsMatch(kname, keyword_pattern))
                            continue;
                        var isAxis = (bool)content[kname]["axis"];
                        var lst = isAxis ? axises : btns;
                        if (lst != null && !lst.Contains(kname))
                        {
                            lst.Add(kname);
                        }
                    }
                }
            }
            catch (System.Exception e)
            {
                Debug.LogException(e);
            }
            foreach (var def in defineFiles)
            {
                List<string> btns;
                List<string> axises;
                if (!buttons.TryGetValue(def, out btns))
                    btns = new List<string>();
                if (!axis.TryGetValue(def, out axises))
                    axises = new List<string>();
                var content = CreateCSharpFile(def, btns, axises);
                var path = Path.Combine(folder, def + ".cs");
                File.WriteAllText(path, content, System.Text.Encoding.UTF8);
                UnityEditor.AssetDatabase.ImportAsset(path);
                Debug.LogFormat("Update Script @ \"{0}\"", path);
            }
            UnityEditor.AssetDatabase.StopAssetEditing();
            //UnityEditor.AssetDatabase.ImportAsset()
        }

        static string CreateCSharpFile(string className, List<string> btns, List<string> axises)
        {
            var buf = ParallelUtils.GetBuilder();
            buf.Append("using GameToolkit;").NextLine().NextLine();
            buf.Append("namespace GameLogic").NextLine();
            var tab = 0;
            buf.Append("{").NextLine();
            tab++;
            buf.AppendTab(tab).Append("using Button = GameInput.IButton;").NextLine();
            buf.AppendTab(tab).Append("using Axis = GameInput.IAxis;").NextLine().NextLine();

            buf.AppendTab(tab).Append("public static class ").Append(className).NextLine();
            buf.AppendTab(tab).Append("{").NextLine();
            tab++;

            buf.AppendTab(tab).Append("static bool _isSetup;").NextLine();
            foreach (var btn in btns)
            {
                var dec = btn.Replace(" ", "_");
                buf.AppendTab(tab).Append("private static Button _").Append(dec).Append(";").NextLine();
                buf.AppendTab(tab).Append("public static Button ").Append(dec).Append(" {get { SetupInput(false); return _").Append(dec).Append("; } }").NextLine();
            }
            foreach (var axis in axises)
            {
                var dec = axis.Replace(" ", "_");
                buf.AppendTab(tab).Append("private static Axis _").Append(dec).Append(";").NextLine();
                buf.AppendTab(tab).Append("public static Axis ").Append(dec).Append(" {get { SetupInput(false); return _").Append(dec).Append("; } }").NextLine();
            }
            buf.NextLine();
            buf.AppendTab(tab).Append("public static void SetupInput(bool reset)").NextLine();
            buf.AppendTab(tab).Append("{").NextLine();
            tab++;

            buf.AppendTab(tab).Append("if(!reset && _isSetup)").NextLine();
            buf.AppendTab(tab + 1).Append("return;").NextLine();
            buf.AppendTab(tab).Append("_isSetup = true;").NextLine();
            foreach (var btn in btns)
            {
                var dec = btn.Replace(" ", "_");
                buf.AppendTab(tab).Append("_").Append(dec).Append(" = GameInput.GetButton(\"").Append(btn).Append("\", true);").NextLine();
            }
            foreach (var axis in axises)
            {
                var dec = axis.Replace(" ", "_");
                buf.AppendTab(tab).Append("_").Append(dec).Append(" = GameInput.GetAxis(\"").Append(axis).Append("\", true);").NextLine();
            }

            tab--;
            buf.AppendTab(tab).Append("}").NextLine();

            buf.AppendTab(tab).Append("public static bool CheckInputModules()").NextLine();
            buf.AppendTab(tab).Append("{").NextLine();
            tab++;

            buf.AppendTab(tab).Append("if (!_isSetup) return false;").NextLine();
            foreach (var btn in btns)
            {
                var dec = btn.Replace(" ", "_");
                buf.AppendTab(tab).Append("if (null == _").Append(dec).Append(")").NextLine();
                buf.AppendTab(tab + 1).Append("return false;").NextLine();
            }
            foreach (var axis in axises)
            {
                var dec = axis.Replace(" ", "_");
                buf.AppendTab(tab).Append("if (null == _").Append(dec).Append(")").NextLine();
                buf.AppendTab(tab + 1).Append("return false;").NextLine();
            }
            buf.AppendTab(tab).Append("return true;").NextLine();

            tab--;
            buf.AppendTab(tab).Append("}").NextLine();

            tab--;
            buf.AppendTab(tab).Append("}").NextLine();

            tab--;
            buf.Append("}").NextLine();

            return ParallelUtils.ReturnRelease(buf);
        }

#endif

        #endregion

        #region Button & Axis

        private class BindingAction
        {
            public readonly int id;
            public readonly InputController.IInputAction action;

            public BindingAction(int id, InputController.IInputAction action)
            {
                this.id = id;
                this.action = action;
            }

            public override int GetHashCode()
            {
                return id;
            }

            public override bool Equals(object obj)
            {
                return action.Equals(obj) || id == obj.GetHashCode();
            }
        }

        private abstract class InputBehaviour
        {
            internal int index;
            int m_Group;
            int m_Id;
            string m_Name;
            protected string m_Desc;
            bool m_IsReadonly;
            public int ID { get { return m_Id; } }
            public int GroupID { get { return m_Group; } }
            public string InputName { get { return m_Name; } }
            public bool IsReadonly { get { return m_IsReadonly; } }
            public string Title { get { return string.IsNullOrEmpty(m_Desc) ? m_Name : m_Desc; } }
            public abstract bool IsVaild { get; }
            public InputBehaviour(string name)
            {
                index = 9999;
                m_Group = 0;
                m_Name = name;
                m_Id = m_Name.IgnoreCaseToHash();
                m_IsReadonly = true;
            }

            internal abstract void ClearActions();

            internal virtual void ReadConfig(GameInput ginput, int group, JsonData data, bool merge)
            {
                if (m_Group == 0 && group != 0)
                    m_Group = group;
                var txt = (string)data["desc"];
                if (!string.IsNullOrEmpty(txt))
                    m_Desc = txt;

                if (!merge)
                {
                    ClearActions();
                    m_IsReadonly = (bool)data["readonly"];
                }
            }

            internal abstract JsonData CreateConfig(GameInput ginput);
            internal abstract bool IsRegist(IInputAction bind);
            internal abstract IInputAction ValidateState(float deltaTime);

            public override string ToString()
            {
                return string.Format("{0}[{1}]", GetType().Name, InputName);
            }

            protected void GetBoolValue(string txt, ref bool defaultValue)
            {
                if (!string.IsNullOrEmpty(txt))
                    defaultValue = Regex.IsMatch(txt, "^(yes|true)$", RegexOptions.IgnoreCase);
            }
        }

        private class Button : InputBehaviour, IButton
        {
            bool mIsUp;
            bool mIsDown;
            bool mHasButton;
            GameInput mInput;
            List<IInputAction> mActions;
            IInputAction mAct;
            public override bool IsVaild { get { return mActions != null && mActions.Count > 0; } }
            public Button(string name) : base(name)
            {
            }

            internal override bool IsRegist(IInputAction bind)
            {
                if (mActions == null || bind == null)
                    return false;
                for (int i = 0; i < mActions.Count; i++)
                {
                    var action = mActions[i];
                    if (action.Equals(bind))
                        return true;
                    if (action is ReferencedAction && ((ReferencedAction)action).IsReferenced(bind))
                        return true;
                }
                return false;
            }

            public KeyInfo GetKeyInfo(InputController controller)
            {
                lock (sLock)
                {
                    if (mActions == null || mActions.Count == 0 || controller == null)
                        return default;
                    IInputAction action = null;
                    bool connected = false;
                    for (int i = 0; i < mActions.Count; i++)
                    {
                        var act = mActions[i];
                        if (act.Controller == controller)
                        {
                            if (controller.IsConnected)
                            {
                                action = act;
                                connected = true;
                                if (act is ReferencedAction
                                    || !(controller is InputController.ISupportKey2)
                                    || ((InputController.ISupportKey2)controller).IsPreferAction(act))
                                    return act.GetKeyInfo();
                            }
                            else if (!connected)
                            {
                                action = act;
                            }
                        }
                    }
                    return action == null ? default : action.GetKeyInfo();
                }
            }

            public bool SetAction(IInputAction action)
            {
                lock (sLock)
                {
                    if (IsReadonly || mActions == null || mActions.Count == 0 || action == null)
                        return false;
                    if (mInput == null || action.Controller == null)
                        return false;
                    var index = -1;
                    for (int i = 0; i < mActions.Count; i++)
                    {
                        var act = mActions[i];
                        if (act.Controller == action.Controller)
                        {
                            if (action.Equals(mActions[i]))
                            {
                                return false;
                            }
                            else if (index == -1)
                            {
                                index = i;
                            }
                        }
                    }
                    if (index == -1)
                        return false;
                    mActions[index] = action;
                    return true;
                }
            }


            internal override void ClearActions()
            {
                if (mActions == null)
                    mActions = new List<IInputAction>();
                else
                    mActions.Clear();
            }

            void ReadAction(GameInput ginput, string bind, JsonData att, bool merge, bool vice)
            {
                var key = (string)att["key"];
                var act = ginput.RegistAction(bind, key);
                if (act != null)
                {
                    if (act is IConfigurable)
                        ((IConfigurable)act).SetConfig(att);
                    if (merge)
                    {
                        for (int i = 0; i < mActions.Count; i++)
                        {
                            var old = mActions[i];
                            if (old.Controller == act.Controller)
                            {
                                if (vice)
                                {
                                    vice = false;
                                    continue;
                                }
                                else
                                {
                                    mActions[i] = act;
                                    break;
                                }
                            }
                        }
                    }
                    else
                    {
                        mActions.Add(act);
                    }
                }
            }

            internal override void ReadConfig(GameInput ginput, int group, JsonData data, bool merge)
            {
                mInput = ginput;
                base.ReadConfig(ginput, group, data, merge);
                var binds = data["bind"];
                foreach (var bind in binds.Keys)
                {
                    var att = binds[bind];
                    if (att == null)
                        continue;
                    if (bind.EndsWith(".vice"))
                        ReadAction(ginput, bind.Substring(0, bind.Length - 5), att, merge, true);
                    else
                        ReadAction(ginput, bind, att, merge, false);
                }
                mAct = mActions.Count == 0 ? null : mActions[0];
            }

            internal override JsonData CreateConfig(GameInput ginput)
            {
                var json = new JsonData(JsonType.Object);
                //json["axis"] = false;
                //json["readonly"] = IsReadonly;
                //json["desc"] = m_Desc.Src;
                var bind = new JsonData(JsonType.Object);
                json["bind"] = bind;
                if (mActions != null)
                {
                    for (int i = 0; i < mActions.Count; i++)
                    {
                        var act = mActions[i];
                        var ct = act.Controller;
                        if (ct == null)
                            continue;
                        JsonData data;
                        if (act is IConfigurable)
                        {
                            data = ((IConfigurable)act).GetConfig();
                        }
                        else
                        {
                            data = new JsonData(JsonType.Object);
                            data["key"] = act.GetKeyInfo().keyName;
                        }
                        if (data == null)
                            continue;
                        if (bind[ct.ConfigName] == null)
                            bind[ct.ConfigName] = data;
                        else
                            bind[ParallelUtils.Concat(ct.ConfigName, ".vice")] = data;
                    }
                }
                return json;
            }

            public bool IsDown { get { return mIsDown; } }

            public bool IsUp { get { return mIsUp; } }

            public bool HasButton { get { return mHasButton; } }

            internal override IInputAction ValidateState(float deltaTime)
            {
                var hadBtn = mHasButton;
                mHasButton = false;
                if (mActions != null)
                {
                    for (int i = 0; i < mActions.Count; i++)
                    {
                        var act = mActions[i];
                        if (act.Controller.IsConnected && act.HasButton && act.Controller != mInput.mHookedController)
                        {
                            mHasButton = true;
                            mAct = act;
                            break;
                        }
                    }
                }
                if (!hadBtn && mHasButton)
                {
                    mIsDown = true;
                    mIsUp = false;
#if DEBUG_ACTION
                    Debug.LogFormat("[{0}] {1} is down", Time.frameCount, InputName);
#endif
                    return mAct;
                }
                else if (hadBtn && !mHasButton)
                {
                    mIsUp = true;
                    mIsDown = false;

#if DEBUG_ACTION
                    Debug.LogFormat("[{0}] {1} is up", Time.frameCount, InputName);
#endif
                }
                else
                {
                    mIsDown = false;
                    mIsUp = false;
                }
                return null;
            }


        }

        private struct AxisValue : System.IEquatable<IInputAction>
        {
            public float sensitivity;
            public IInputAction action;

            public AxisValue(float sensitivity, IInputAction action)
            {
                this.sensitivity = sensitivity;
                this.action = action;
            }

            public bool Equals(IInputAction other)
            {
                return action == other;
            }

            public override bool Equals(object obj)
            {
                if (action == null)
                    return obj == null;
                else
                    return action.Equals(obj);
            }

            public override int GetHashCode()
            {
                return action == null ? 0 : action.GetHashCode();
            }
        }

        private class Axis : InputBehaviour, IAxis
        {
            float mValue;
            bool mHasBtn;
            bool mIsUp;
            bool mIsDown;
            bool mClamp01;
            string mNigativeDesc;
            string mPositiveDesc;
            List<AxisValue> mAxises;
            IInputAction mAct;
            GameInput mInput;
            public override bool IsVaild { get { return mAxises != null && mAxises.Count > 0; } }
            public string NigativeTitle { get { return mNigativeDesc; } }
            public string PositiveTitle { get { return mPositiveDesc; } }
            public Axis(string name) : base(name)
            {
                mClamp01 = true;
            }

            internal override bool IsRegist(IInputAction bind)
            {
                if (mAxises == null || bind == null)
                    return false;
                for (int i = 0; i < mAxises.Count; i++)
                {
                    var action = mAxises[i];
                    if (action.Equals(bind))
                        return true;
                    if (action.action is ReferencedAction && ((ReferencedAction)action.action).IsReferenced(bind))
                        return true;
                }
                return false;
            }

            public void GetKeyInfo(InputController controller, out KeyInfo nigative, out KeyInfo positive)
            {
                lock (sLock)
                {
                    if (mAxises == null || mAxises.Count == 0 || controller == null)
                    {
                        nigative = default;
                        positive = default;
                        return;
                    }

                    IInputAction action = null;
                    for (int i = 0; i < mAxises.Count; i++)
                    {
                        var act = mAxises[i];
                        if (act.action.Controller == controller)
                        {
                            action = act.action;
                            if (act.action.Controller.IsConnected)
                                break;
                        }
                    }
                    if (action is ICombinedAxis)
                    {
                        nigative = ((ICombinedAxis)action).GetNigativeKeyInfo();
                        positive = ((ICombinedAxis)action).GetPositiveKeyInfo();
                    }
                    else if(action != null)
                    {
                        nigative = action.GetKeyInfo();
                        positive = nigative;
                    }
                    else
                    {
                        nigative = default;
                        positive = default;
                    }
                }
            }

            public bool SetAction(IInputAction action)
            {
                lock (sLock)
                {
                    if (IsReadonly || mAxises == null || mAxises.Count == 0 || action == null)
                        return false;
                    if (mInput == null || action.Controller == null)
                        return false;
                    int index = -1;
                    for (int i = 0; i < mAxises.Count; i++)
                    {
                        var axis = mAxises[i];
                        var ct = axis.action.Controller;
                        if (ct == action.Controller)
                        {
                            if (axis.action.Equals(action))
                                return false;
                            else if (index == -1)
                                index = i;
                        }
                    }
                    if (index == -1)
                    {
                        return false;
                    }
                    else
                    {
                        var axis = mAxises[index];
                        axis.action = action;
                        mAxises[index] = axis;
                        return true;
                    }
                }
            }

            internal override void ClearActions()
            {
                if (mAxises == null)
                    mAxises = new List<AxisValue>();
                else
                    mAxises.Clear();
            }

            void ReadAction(GameInput ginput, string bind, JsonData cfg, bool merge, bool vice)
            {
                IInputAction act;
                float sensi;
                if (cfg.ContainsKey("sensitivity"))
                    sensi = (float)cfg["sensitivity"];
                else
                    sensi = 1f;
                var axis = (string)cfg["axis"];
                if (string.IsNullOrEmpty(axis))
                {
                    var pos = (string)cfg["positive"];
                    var nig = (string)cfg["nigative"];
                    act = ginput.RegistAxis(bind, pos, nig);
                }
                else
                {
                    act = ginput.RegistAction(bind, axis);
                }
                if (act != null)
                {
                    if (act is IConfigurable)
                        ((IConfigurable)act).SetConfig(cfg);
                    if (merge)
                    {
                        for (int i = 0; i < mAxises.Count; i++)
                        {
                            var old = mAxises[i];
                            if (old.action.Controller == act.Controller)
                            {
                                if (vice)
                                {
                                    vice = false;
                                    continue;
                                }
                                else
                                {
                                    mAxises[i] = new AxisValue(sensi, act);
                                    break;
                                }
                            }
                        }
                    }
                    else
                    {
                        mAxises.Add(new AxisValue(sensi, act));
                    }
                }
            }

            internal override void ReadConfig(GameInput ginput, int group, JsonData data, bool merge)
            {
                mInput = ginput;
                base.ReadConfig(ginput, group, data, merge);
                if (!merge)
                {
                    mClamp01 = (bool)data["clamp"];
                    mNigativeDesc = (string)data["desc.nigative"];
                    mPositiveDesc = (string)data["desc.positive"];
                }
                var binds = data["bind"];
                foreach (var bind in binds.Keys)
                {
                    var cfg = binds[bind];
                    if (bind.EndsWith(".vice"))
                        ReadAction(ginput, bind.Substring(0, bind.Length - 5), cfg, merge, true);
                    else
                        ReadAction(ginput, bind, cfg, merge, false);
                }
                mAct = mAxises.Count == 0 ? null : mAxises[0].action;
            }

            internal override JsonData CreateConfig(GameInput ginput)
            {
                var data = new JsonData(JsonType.Object);
                //data["axis"] = true;
                //data["readonly"] = IsReadonly;
                //data["desc"] = m_Desc.Src;
                //data["clamp"] = mClamp01;
                var bind = new JsonData(JsonType.Object);
                data["bind"] = bind;
                if (mAxises != null)
                {
                    for (int i = 0; i < mAxises.Count; i++)
                    {
                        var axis = mAxises[i];
                        var ct = axis.action.Controller;
                        if (ct == null)
                            continue;
                        JsonData cfg;
                        if (axis.action is IConfigurable)
                        {
                            cfg = ((IConfigurable)axis.action).GetConfig();
                        }
                        else
                        {
                            var keyinfo = axis.action.GetKeyInfo();
                            cfg = new JsonData();
                            cfg["axis"] = keyinfo.keyName;
                        }
                        cfg["sensitivity"] = axis.sensitivity;
                        if (bind.ContainsKey(ct.ConfigName))
                            bind[ParallelUtils.Concat(ct.ConfigName, ".vice")] = cfg;
                        else
                            bind[ct.ConfigName] = cfg;
                    }
                }
                return data;
            }

            public float Value { get { return mValue; } }

            public bool IsDown { get { return mIsDown; } }
            public bool HasButton { get { return mHasBtn; } }
            public bool IsUp { get { return mIsUp; } }

            internal override IInputAction ValidateState(float deltaTime)
            {
                bool hadBtn = mHasBtn;
                mHasBtn = false;
                mValue = 0;
                if (mAxises != null)
                {
                    for (int i = 0; i < mAxises.Count; i++)
                    {
                        var axis = mAxises[i];
                        var ctrl = axis.action.Controller;
                        if (!ctrl.IsConnected || ctrl == mInput.mHookedController)
                            continue;
                        mValue += axis.action.Value * axis.sensitivity;
                        if (!mHasBtn && axis.action.HasButton)
                        {
                            mAct = axis.action;
                            mHasBtn = true;
                        }
                    }
                }
                if (mClamp01)
                {
                    if (mValue < -1)
                        mValue = -1;
                    else if (mValue > 1)
                        mValue = 1;
                }
                if (mHasBtn && !hadBtn)
                {
                    mIsDown = true;
                    mIsUp = false;
                    return mAct;
                }
                else if (!mHasBtn && hadBtn)
                {
                    mIsDown = false;
                    mIsUp = true;
                }
                else
                {
                    mIsDown = false;
                    mIsUp = false;
                }
                return null;
            }

        }

        private abstract class ReferencedAction
        {
            public abstract bool IsReferenced(IInputAction bind);
        }

        // 由两个按键组成的轴
        private class AxisFromAction : ReferencedAction, ICombinedAxis, ITick, IConfigurable
        {
            IInputAction mNigative;
            IInputAction mPositive;
            float mValue;
            float mAcc;
            float mDrag;
            bool mHasBtn;
            public float Value { get { return mValue; } }
            public bool HasButton { get { return mHasBtn; } }

            InputController mCtrl;
            public InputController Controller { get { return mCtrl; } }
            public EControllerType ControllerType { get { return mCtrl.ControllerType; } }
            public AxisFromAction(IInputAction nigative, IInputAction positive)
            {
                mNigative = nigative;
                mPositive = positive;
                mAcc = 100f;
                mDrag = 100f;
                if (positive != null)
                    mCtrl = mPositive.Controller;
                else if (mNigative != null)
                    mCtrl = mNigative.Controller;
            }

            public KeyInfo GetKeyInfo()
            {
                if (mValue < 0 && mNigative != null)
                    return mNigative.GetKeyInfo();
                else if (mPositive != null)
                    return mPositive.GetKeyInfo();
                else
                    return default;
            }

            public KeyInfo GetNigativeKeyInfo()
            {
                return mNigative == null ? default : mNigative.GetKeyInfo();
            }

            public KeyInfo GetPositiveKeyInfo()
            {
                return mPositive == null ? default : mPositive.GetKeyInfo();
            }

            public void OnTick(float deltaTime)
            {
                float v = 0;
                mHasBtn = false;
                if (mNigative != null && mNigative.HasButton)
                {
                    v -= 1f;
                    mHasBtn = true;
                    //mCtrl = mNigative.Controller;
                }
                if (mPositive != null && mPositive.HasButton)
                {
                    v += 1f;
                    mHasBtn = true;
                    //mCtrl = mPositive.Controller;
                }
                mValue = Mathf.MoveTowards(mValue, v, (mHasBtn ? mAcc : mDrag) * deltaTime);
            }

            public void SetConfig(JsonData cfg)
            {
                if (cfg.ContainsKey("acc"))
                    mAcc = (float)cfg["acc"];
                if (cfg.ContainsKey("drag"))
                    mDrag = (float)cfg["drag"];
            }

            public JsonData GetConfig()
            {
                if (mCtrl == null)
                    return null;
                var cfg = new JsonData(JsonType.Object);
                if (mNigative != null)
                    cfg["nigative"] = mNigative.GetKeyInfo().keyName;
                if (mPositive != null)
                    cfg["positive"] = mPositive.GetKeyInfo().keyName;
                cfg["acc"] = mAcc;
                cfg["drag"] = mDrag;
                return cfg;
            }

            public override bool IsReferenced(InputController.IInputAction bind)
            {
                return mNigative != null && mNigative.Equals(bind) ||
                    mPositive != null && mPositive.Equals(bind);
            }
        }

        private class ActionFromValue : ReferencedAction, IInputAction, IConfigurable
        {
            IInputAction mAxis;
            EAxisValue mValueType; // 0: both, 1: 
            float mThreshold;
            public InputController Controller { get { return mAxis.Controller; } }

            public bool HasButton
            {
                get
                {
                    if (mAxis.HasButton)
                    {
                        if (mValueType == EAxisValue.Nigative)
                            return mAxis.Value < mThreshold;
                        else if (mValueType == EAxisValue.Positive)
                            return mAxis.Value > mThreshold;
                        else
                            return true;
                    }
                    return false;
                }
            }

            public float Value { get { return mValueType == EAxisValue.Nigative ? -mAxis.Value : mAxis.Value; } }
            public EControllerType ControllerType { get { return mAxis.ControllerType; } }
            public ActionFromValue(IInputAction axis, EAxisValue valueType)
            {
                mAxis = axis;
                mValueType = valueType;
                if (valueType == EAxisValue.Nigative)
                    mThreshold = -0.5f;
                else
                    mThreshold = 0.5f;
            }

            public KeyInfo GetKeyInfo()
            {
                var key = mAxis.GetKeyInfo();
                //if ((key.keyName.Equals("LT") || key.keyName.Equals("RT")) && (mActiveGamepad == "PS4"))
                //    return key;
                if (mValueType == EAxisValue.Nigative)
                    key.keyName = ParallelUtils.Concat('-', key.keyName);
                else if (mValueType == EAxisValue.Positive)
                    key.keyName = ParallelUtils.Concat('+', key.keyName);
                return key;
            }

            public void SetConfig(JsonData cfg)
            {
                if (cfg.ContainsKey("threshold"))
                {
                    var thres = (float)cfg["threshold"];
                    if ((mValueType == EAxisValue.Nigative) ^ (thres < 0))
                        mThreshold = -thres;
                    else
                        mThreshold = thres;
                }
            }

            public JsonData GetConfig()
            {
                var ct = mAxis.Controller;
                if (ct == null)
                    return null;
                var key = GetKeyInfo();
                var cfg = new JsonData(JsonType.Object);
                cfg["key"] = key.keyName;
                var thres = mThreshold < 0 ? -mThreshold : mThreshold;
                cfg["threshold"] = thres;
                return cfg;
            }

            public override bool IsReferenced(IInputAction bind)
            {
                return mAxis.Equals(bind);
            }
        }

        #endregion

        #region Hook InputController
        IInputActionHook mHook;
        InputController mHookedController;
        List<Hook> mHooks;
        // 绑定输入钩子，截断输入
        public bool AttachInputHook(InputController ctrl, IInputActionHook hookAction)
        {
            if (ctrl == null || hookAction == null)
                throw new System.ArgumentNullException("InputController & IInputActionHook should not be null");
            if (hookAction == mHook)
                return true;
            bool hasCtrl = false;
            var len = m_InputModules == null ? 0 : m_InputModules.Length;
            for (int i = 0; !hasCtrl && i < len; i++)
            {
                hasCtrl = m_InputModules[i] == ctrl;
            }
            if (!hasCtrl)
                return false;
            var keys = ctrl.GetKeysInfo();
            if (mHook != null)
            {
                mHook.OnDettached(mHookedController);
            }
            if (mHooks == null)
                mHooks = new List<Hook>(keys.Length);
            else
                mHooks.Clear();
            mHook = hookAction;
            mHookedController = ctrl;
            for (int i = 0; i < keys.Length; i++)
            {
                var action = RegistAction(mHookedController, new NameID(keys[i].keyName));
                if (action != null)
                {
                    mHooks.Add(new Hook(action));
                }
            }
            mHook.OnAttached(ctrl);
            return true;
        }

        // 取消输入钩子
        public void DettachInputHook(IInputActionHook hook)
        {
            if (hook == mHook && hook != null)
            {
                if (mHook != null)
                    mHook.OnDettached(mHookedController);
                mHook = null;
                mHookedController = null;
                if (mHooks != null)
                    mHooks.Clear();
                UnregistBindingActions();
            }
        }

        void ValidateInputActionHooks()
        {
            if (mHooks != null)
            {
                for (int i = 0; i < mHooks.Count; i++)
                {
                    mHooks[i].GetHook(this);
                }
            }
        }

        private class Hook
        {
            IInputAction mAction;
            bool mHasBtn;
            bool mIsDown;
            bool mIsUp;
            bool mNigative;

            internal Hook(IInputAction action)
            {
                mAction = action;
                mHasBtn = action.HasButton;
            }

            public void GetHook(GameInput ginput)
            {
                var hadBtn = mHasBtn;
                mHasBtn = mAction.HasButton;
                if(mHasBtn)
                {
                    mNigative = mAction.Value < 0;
                }
                if(!hadBtn && mHasBtn)
                {
                    mIsDown = true;
                }
                else if(hadBtn && !mHasBtn)
                {
                    mIsUp = true;
                    if(mIsDown && mIsUp && ginput.mHook != null)
                    {
                        var key = mAction.GetKeyInfo();
                        if(key.isAxis)
                        {
                            key.keyName = ParallelUtils.Concat(mNigative ? '-' : '+', key.keyName);
                        }
                        ginput.mHook.OnGetAction(key);
                    }
                }
                if (!mHasBtn)
                {
                    mIsDown = false;
                    mIsUp = false;
                }
            }
        }

        #endregion
    }
}